package frontend.visitor.symboltbl;

import exceptions.VisitInnerException;
import frontend.visitor.symboltbl.scope.FunctionScope;
import frontend.visitor.symboltbl.scope.InnerScope;
import frontend.visitor.symboltbl.scope.RootScope;
import frontend.visitor.symboltbl.scope.Scope;
import ir.values.Argument;
import ir.values.FunctionIr;

import java.util.Stack;

public class SymbolTbl {
    private final RootScope rootScope;
    private final Stack<Scope> scopeStack = new Stack<>();
    private FunctionScope curFunctionScope = null;
    public SymbolTbl() {
        rootScope = new RootScope();
        rootScope.setScopeStackTrace(new ScopeStackTrace(rootScope));
        scopeStack.push(rootScope);
    }
    public void pushInnerScope() {
        Scope scope = new InnerScope(peekScope().getChildrenNum());
        scope.setScopeStackTrace(new ScopeStackTrace(peekScope().getScopeStackTrace(), scope));
        peekScope().addChildScope(scope);
        scopeStack.push(scope);
    }
    public void putFunctionSymbolAndScope(FunctionIr functionIr) {
        if (scopeStack.size() != 1) throw new VisitInnerException();
        if (functionIr.getOriginalName() == null) throw new VisitInnerException();

        // put function symbol
        if (!rootScope.putFunctionSymbol(functionIr.getOriginalName(), functionIr)) throw new VisitInnerException();

        // push scope
        FunctionScope functionScope = new FunctionScope(functionIr);
        functionScope.setScopeStackTrace(new ScopeStackTrace(rootScope.getScopeStackTrace(), functionScope));
        scopeStack.push(functionScope);
        this.curFunctionScope = functionScope;

    }
    public void putArgumentSymbols() {
        for (Argument argument: this.curFunctionScope.getFunctionIr().getArguments()) {
            this.curFunctionScope.putSymbol(argument.getOriginalName(), new Symbol(argument.getPointerValue()));
        }
    }
    public void popScope() {
        Scope scope = scopeStack.pop();
        if (scope instanceof FunctionScope) {
            this.curFunctionScope = null;
        }
    }
    public Scope peekScope() {
        return scopeStack.peek();
    }
    public boolean inRootScope() {
        return scopeStack.size() == 1;
    }
    public boolean duplicateCheck(String ident) {
        return peekScope().duplicateCheck(ident);
    }
    public void putSymbol(Symbol symbol) {
        if (symbol.getName() == null) throw new VisitInnerException();
        peekScope().putSymbol(symbol.getName(), symbol);
    }
    public Symbol findSymbol(String ident) {
        for (int i = scopeStack.size() - 1; i >= 0; i--) {
            Symbol symbol = scopeStack.get(i).findSymbol(ident);
            if (symbol != null) {
                return symbol;
            }
        }
        return null;
    }
    public FunctionIr findFunctionSymbol(String ident) {
        return rootScope.findFunctionSymbol(ident);
    }

    public RootScope getRootScope() {
        return rootScope;
    }
    public boolean isGlobalScope() {
        return scopeStack.size() == 1;
    }
    public FunctionIr getCurFunction() {
        if (curFunctionScope == null) return null;
        return curFunctionScope.getFunctionIr();
    }
}

